package com.colloquial.arithcode2;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;

import model.archivo.ArchivoDiccionario;

public class PPMCompresor {

	private int orden;
	
	private String contextoGlobal;

	private int modeloActual;
	
	private String contextoActual;

	private int modeloGlobal;

	private ArrayList<RegistroSimbolos> listaCeros = new ArrayList<RegistroSimbolos>(65538);
	
	private ArrayList<RegistroSimbolos> listaModeloMenos1 = new ArrayList<RegistroSimbolos>(65538);
	
	CompresorAritmetico compresArit;
	
	DescompresorAritmetico descArit;

	private ArchivoDiccionario archivoModelo;

	private ArrayList<PPMContexto> listaPPMContexto = new ArrayList<PPMContexto>();

	//private static final byte[] ESC = codificador.toBytes((short) 65535);
	private static int ESC = 65536;

	private static int EOF = 65537;
	
	private int tam1;
	private int tam2;
	private int tam3;
	private int tam4;
	//private int tam0;
	
	
	private ArrayList<Integer> simbolosExcluidos = new ArrayList<Integer>();

	//el constructor crea los archivos de los modelos 
	public PPMCompresor(int NewOrden) throws Exception {
		this.orden = NewOrden;
		this.contextoGlobal = null;
		this.modeloGlobal = 0;
		tam1=tam2=tam3=tam4=0;
		
		compresArit = new CompresorAritmetico();
		generarListaDeUnos(listaModeloMenos1);

		generarListaDeCeros(listaCeros);

		crearArchivos(this.orden);
	}

	private void generarListaDeCeros(ArrayList<RegistroSimbolos> lista2) throws Exception {
		RegistroSimbolos rs = new RegistroSimbolos();
		
		rs.setProbabilidad(0);
		lista2.clear();
		for (int i = 0; i <= 65537; i++) {
			lista2.add(i, rs);
		}		
	}
	
	private void generarListaDeUnos(ArrayList<RegistroSimbolos> lista2) {
		RegistroSimbolos rs = new RegistroSimbolos();
		rs.setProbabilidad(1);
		lista2.clear();
		for (int i = 0; i <= 65537; i++) {
			lista2.add(i, rs);
		}		
	}
	

	//debo modificar el ctx actual
	public void PPMCompress(Integer simboloAComprimir ) throws Exception {
		
		String simbolo = new String(simboloAComprimir.toString());
	     		
		int comprimido = 0; //0 = false
	
		modeloActual = modeloGlobal;
		
		contextoActual = contextoGlobal;
		System.out.println();
		System.out.println("se comprime: "+simbolo);
			
		do {
			System.out.println();
			
			
			if (modeloActual != -1) {
				boolean ctxEncontrado = false;
				this.archivoModelo = abrirArchivoModeloActual(this.modeloActual);
				archivoModelo.rwArchivo();

				// cargo la lista de contextos que esta en el archivo segun el modelo
				cargarListaContextosModeloActual();

				
				// recorro la lista de contextos al menos que este vacia y el
				// modelo sea mayor a 0
				if (!this.listaPPMContexto.isEmpty() && (modeloActual > 0)) {
					for (PPMContexto contextoAux : listaPPMContexto) {
						// los modelos -1 y 0 tienen contextos nulos
						if (contextoAux.getContexto().equals(contextoActual)) {
							// encotre el contexto, ahora busco el simbolo
							comprimido = comprimirEnContexto(contextoAux,simboloAComprimir);
							ctxEncontrado = true;
							break;
						}
					}// End for listaContexto
					if (ctxEncontrado == false) { // no encontre el contexto
						agregoContextoAlModelo(listaPPMContexto,contextoActual, simboloAComprimir);
						//agregoSimboloAlContexto(contextoAux, simboloAComprimir)
						
						//emito(ESC, listaDefaultESC, simbolosExcluidos);
					}

				} // fin lista ctx vacia o modelo > 0
				else {
					if (modeloActual == 0) { // no tiene contextos, solamente
						// revisa los simbolos
						PPMContexto contextoAux = listaPPMContexto.get(0);
						comprimido = comprimirEnContexto(contextoAux,simboloAComprimir);
					} else{
						agregoContextoAlModelo(listaPPMContexto, contextoActual, simboloAComprimir);
					    //emito(ESC, listaDefaultESC, simbolosExcluidos);
						}
				}
				
				cierroArhivo(archivoModelo, modeloActual);

				this.archivoModelo.cerrarArchivo();
				

				this.modeloActual--;
				
				if (modeloActual > 0){
					disminuyoContexto(contextoActual, modeloGlobal - modeloActual);
					//disminuyoContexto(contextoActual, modeloActual);
				}			
			}	
			
			if ((modeloActual == -1)&&(comprimido == 0)) {
				emitoUltimoNivel(simboloAComprimir, listaModeloMenos1);
			}
			
		}while ((modeloActual >= 0) && (comprimido == 0));

		
		
		if (modeloGlobal < orden){
			modeloGlobal++;
			aumentoContexto(simbolo);
		}else
			corroContexto(simbolo);

		modificarTamanio(modeloGlobal, simbolo.length());

	}
	
	public void PPMCDescompress (byte[] charAComp) throws BitArrayException{
		
		BitArrayStream carADes = new BitArrayStream(charAComp);
        descArit = new DescompresorAritmetico(carADes);
        
        boolean eof;
        
        do{
        	int comprimido = 0; //0 = false
        	
    		modeloActual = modeloGlobal;
    		
    		contextoActual = contextoGlobal;
    		System.out.println();
    		
    		if (modeloActual != -1) {
				boolean ctxEncontrado = false;
				this.archivoModelo = abrirArchivoModeloActual(this.modeloActual);
				archivoModelo.rwArchivo();

				// cargo la lista de contextos que esta en el archivo segun el modelo
				cargarListaContextosModeloActual();
        
				if (!this.listaPPMContexto.isEmpty() && (modeloActual > 0)) {
					for (PPMContexto contextoAux : listaPPMContexto) {
						// los modelos -1 y 0 tienen contextos nulos
						if (contextoAux.getContexto().equals(contextoActual)) {
							// encotre el contexto, ahora busco el simbolo
							comprimido = descomprimirEnContexto(contextoAux);
							ctxEncontrado = true;
							break;
						}
					}// End for listaContexto
					if (ctxEncontrado == false) { // no encontre el contexto
						agregoContextoAlModelo(listaPPMContexto,contextoActual, simboloAComprimir);
						//agregoSimboloAlContexto(contextoAux, simboloAComprimir)
						
						//emito(ESC, listaDefaultESC, simbolosExcluidos);
					}

				}    		
    		
    		
    		
    		}
        
        
	}

	private int descomprimirEnContexto(PPMContexto contextoAux) {
         TablaProbabilidades TablaP = new TablaProbabilidades();
         
         TablaP.setATablaProbabilidades(tablaProbabilidades)
		
         descArit.descomprimirSiguientePaso() 
		
		if (contextoAux.getArraySimbolos().get(simboloAComprimir).getProbabilidad() != (float) 0) {
			emito(simboloAComprimir, contextoAux.getArraySimbolos());
			simbolosExcluidos.clear();
			return 1;//true;
			
		} else {
			//copiarListas(contextoAux.getArraySimbolos());
			//agregoSimboloAlContexto(contextoAux, simboloAComprimir);
			emito(ESC, contextoAux.getArraySimbolos());
			copiarListas(contextoAux.getArraySimbolos());
			agregoSimboloAlContexto(contextoAux.getArraySimbolos(), simboloAComprimir);						
			return 0;//false
		}
		
		
		
		
		
		return 0;
	}

	private void corroContexto(String simbolo) {
		int largo = contextoGlobal.length();
		String aux = contextoGlobal.substring(this.tam1, largo);
		aux = aux + simbolo;
		contextoGlobal = aux;
		
	}

	private void aumentoContexto(String caracter) {
		if (modeloGlobal == 1)
			contextoGlobal = caracter;
		else
			contextoGlobal = contextoGlobal + caracter;
	}

	/*
	private void disminuyoContexto(String ctxActual, int modelo) {
		int tamanio = 0;
		int largo = 0;
		switch(modelo) {
		case 1:
			 tamanio = this.tam1;
			break;
		case 2:
			tamanio = this.tam1;
			//tamanio = this.tam1 + this.tam2;
			break;

		case 3:
			tamanio = this.tam1; //+ this.tam2;
			//tamanio = this.tam1 + this.tam2 + this.tam3;
		    break;

		case 4:
			tamanio = this.tam1; //+ this.tam2 + this.tam3;
			//tamanio = this.tam1 + this.tam2 + this.tam3 + this.tam4;
		    break;
		}
		
		
		largo = ctxActual.length();
		
		String aux = ctxActual.substring(tamanio,largo);
		this.contextoActual = aux;
	}

*/
	private void disminuyoContexto(String ctxActual, int modelo) {
		int tamanio = 0;
		int largo = 0;
		switch(modelo) {
		case 1:
			 tamanio = this.tam1;
			break;
		case 2:
			tamanio = this.tam2;
			//tamanio = this.tam1 + this.tam2;
			break;

		case 3:
			tamanio = this.tam3; //+ this.tam2;
			//tamanio = this.tam1 + this.tam2 + this.tam3;
		    break;

		case 4:
			tamanio = this.tam4; //+ this.tam2 + this.tam3;
			//tamanio = this.tam1 + this.tam2 + this.tam3 + this.tam4;
		    break;
		}
		
		
		largo = ctxActual.length();
		
		String aux = ctxActual.substring(tamanio,largo);
		this.contextoActual = aux;
	}
	
	
	private int comprimirEnContexto(PPMContexto contextoAux, int simboloAComprimir) throws Exception {
		if (contextoAux.getArraySimbolos().get(simboloAComprimir).getProbabilidad() != (float) 0) {
			emito(simboloAComprimir, contextoAux.getArraySimbolos());
			simbolosExcluidos.clear();
			return 1;//true;
			
		} else {
			//copiarListas(contextoAux.getArraySimbolos());
			//agregoSimboloAlContexto(contextoAux, simboloAComprimir);
			emito(ESC, contextoAux.getArraySimbolos());
			copiarListas(contextoAux.getArraySimbolos());
			agregoSimboloAlContexto(contextoAux.getArraySimbolos(), simboloAComprimir);						
			return 0;//false
		}
	}

	
	private void modificarTamanio(int modelo, int tamanio){
		switch(modelo) {
		case 1:
			this.tam1 = tamanio;
			break;
		case 2:
			if(tam2!=0)
				this.tam1=this.tam2;
			this.tam2 = tamanio;
			break;

		case 3:
			if (tam3!=0){
				this.tam1= this.tam2;
				this.tam2= this.tam3;
			}
		    
			this.tam3= tamanio;
		    break;

		case 4:
			if (tam4!=0){
			this.tam1= this.tam2;
		    this.tam2= this.tam3;
		    this.tam3= this.tam4;
			}
		    this.tam4 = tamanio;
		    break;
		}
	}
	
	
	private void copiarListas(ArrayList<RegistroSimbolos> arraySimbolos) {
		for (int i = 0; i < 65536; i++) { //estoy omitiendo el ESC y el EOF
			RegistroSimbolos rs = new RegistroSimbolos();
			rs = arraySimbolos.get(i);
			if (rs.getProbabilidad() != 0) {
				if (!this.simbolosExcluidos.contains(i))
					this.simbolosExcluidos.add(i);
			}
		}
	}

	
	//agrego el contexto al modelo y ademas le agrego el simboloAComprimir
	private void agregoContextoAlModelo(ArrayList<PPMContexto> NewPpmcListaContexto, String NuevoContexto, int NewSimboloAComprimir) throws Exception {
		
		PPMContexto PPMCcontextoAAgregar = new PPMContexto(NuevoContexto);
		//ArrayList<RegistroSimbolos> listaDefault = new ArrayList<RegistroSimbolos>();
		//modificarProbabilidadACero(listaDefault);
		//PPMCcontextoAAgregar.setArraySimbolos(listaDefault);
		emito(ESC, PPMCcontextoAAgregar.getArraySimbolos());
		agregoSimboloAlContexto(PPMCcontextoAAgregar.getArraySimbolos(), NewSimboloAComprimir);
		NewPpmcListaContexto.add(PPMCcontextoAAgregar);
	}
	

	private void agregoSimboloAlContexto(ArrayList<RegistroSimbolos> ListaRegistros, int simboloAComprimir2) {
		RegistroSimbolos rs = new RegistroSimbolos();		
		rs.setProbabilidad(1);
		ListaRegistros.set(simboloAComprimir2, rs);
	}

	
	private void emitoUltimoNivel(int simbolo, ArrayList<RegistroSimbolos> lista2) throws Exception {
		Long contador = 0L;
		ArrayList<RegistroSimbolos> ultimaLista= new ArrayList<RegistroSimbolos>(65538);
		ultimaLista = aplicarExclusion(lista2);
		contador = caluloTotal(ultimaLista);
						
		System.out.println("Modelo: " + modeloActual + "\tContexto: " + contextoActual);
		System.out.println("Simbolo: " + simbolo + "\tFrecuencia: "+ (float) ultimaLista.get(simbolo).getProbabilidad()/contador);
		System.out.println();
		comprimirConArit(ultimaLista,simbolo, contador);
		simbolosExcluidos.clear();
	}
	

	private void emito(int simbolo, ArrayList<RegistroSimbolos> lista2) throws Exception {
		
		ArrayList<RegistroSimbolos> ultimaLista= new ArrayList<RegistroSimbolos>();
		Long contador = 0L;
		/*
		System.out.println("Modelo: " + modeloActual + "\tContexto: " + contextoActual);
		ultimaLista = aplicarExclusion(lista2);
		System.out.println("Simbolo: " + simbolo + "\tFrecuencia: "+ ultimaLista.get(simbolo).getProbabilidad());
		System.out.println();
		float frecuenciaIncial = lista2.get(simbolo).getProbabilidad();
		lista2.get(simbolo).setProbabilidad(frecuenciaIncial + 1);
		*/
		if(simbolo!= ESC){
			System.out.println("Modelo: " + modeloActual + "\tContexto: " + contextoActual);
			ultimaLista = aplicarExclusion(lista2);
			contador = caluloTotal(ultimaLista);
			
			comprimirConArit(ultimaLista,simbolo, contador);			
			System.out.println("Simbolo: " + simbolo + "\tFrecuencia: "+ ultimaLista.get(simbolo).getProbabilidad()/contador);
			System.out.println();
			long frecuenciaIncial = lista2.get(simbolo).getProbabilidad();
			lista2.get(simbolo).setProbabilidad(frecuenciaIncial + 1);
		}
		else{//el simbolo es el ESC	
			if (lista2.get(simbolo).getProbabilidad() == (float) 0) {
				RegistroSimbolos rs = new RegistroSimbolos();
				rs.setProbabilidad(1);
				//entonces no hay nada en la lista y tengo que emitir ESC(1)
				lista2.set(simbolo, rs);
				System.out.println("Modelo: " + modeloActual + "\tContexto: " + contextoActual);
				System.out.println("Simbolo: " + simbolo + "\tFrecuencia: "+ 1);
				System.out.println();
				
			}else{
				//copiarListas(lista2);
				ultimaLista = aplicarExclusion(lista2);
				contador = caluloTotal(ultimaLista);
				System.out.println("Modelo: " + modeloActual + "\tContexto: " + contextoActual);
				System.out.println("Simbolo: " + simbolo + "\tFrecuencia: "+ (float) ultimaLista.get(simbolo).getProbabilidad()/contador);
				System.out.println();
				
				comprimirConArit(ultimaLista,simbolo, contador);
				
				long frecuenciaIncial = lista2.get(simbolo).getProbabilidad();
				lista2.get(simbolo).setProbabilidad(frecuenciaIncial + 1);
			}
		}
	}
	
	private void comprimirConArit(ArrayList<RegistroSimbolos> ultimaLista, int simbolo, Long contador) throws Exception {
		
		TablaProbabilidades tablaP = new TablaProbabilidades();
		tablaP.setATablaProbabilidades(ultimaLista);
		tablaP.setTotal(contador);
		this.compresArit.calcularIntervalo(tablaP, simbolo);
		
	}

	private Long caluloTotal(ArrayList<RegistroSimbolos> ultimaLista) {
		Long contador = 0L;
		for (int i = 0; i <=65537 ; i++){
				Long proba = ultimaLista.get(i).getProbabilidad();
				contador = contador+proba;
				
			}	
		
		
		return contador;
	}

	/*
	private void emito(int simbolo, ArrayList<RegistroSimbolos> lista2, ArrayList<Integer> simbolosExcluidos2) {		
		if(simbolo!= ESC){
			System.out.println("Modelo: " + modeloActual + "\tContexto: " + contextoActual);
			System.out.println("Simbolo: " + simbolo + "\tFrecuencia: "+ lista2.get(simbolo).getProbabilidad());
			System.out.println();
			float frecuenciaIncial = lista2.get(simbolo).getProbabilidad();
			lista2.get(simbolo).setProbabilidad(frecuenciaIncial + 1);
		}
		else{//el simbolo es el ESC	
			if (lista2.get(simbolo).getProbabilidad() == (float) 0) {
				//entonces no hay nada en la lista y tengo que emitir ESC(1)
				lista2.get(simbolo).setProbabilidad(1);
				System.out.println("Modelo: " + modeloActual + "\tContexto: " + contextoActual);
				System.out.println("Simbolo: " + simbolo + "\tFrecuencia: "+ 1);
				System.out.println();
			}else{
				System.out.println("Modelo: " + modeloActual + "\tContexto: " + contextoActual);
				System.out.println("Simbolo: " + simbolo + "\tFrecuencia: "+ lista2.get(simbolo).getProbabilidad());
				System.out.println();
				
				float frecuenciaIncial = lista2.get(simbolo).getProbabilidad();
				lista2.get(simbolo).setProbabilidad(frecuenciaIncial + 1);
			}
		}
	}
	*/


	private ArrayList<RegistroSimbolos> aplicarExclusion(ArrayList<RegistroSimbolos> listaRegistro) throws Exception {
		ArrayList<RegistroSimbolos> listaAux = new ArrayList<RegistroSimbolos>(65538);
		
		generarListaDeCeros(listaAux);		
		
		for (int i = 0; i <=65537 ; i++){
			if (!this.simbolosExcluidos.contains(i)){
			
				RegistroSimbolos rsAux = new RegistroSimbolos();
				Long proba = listaRegistro.get(i).getProbabilidad();
				rsAux.setProbabilidad(proba);
				listaAux.set(i,rsAux);
			}				
		}
		/*
		for (int i = 0; i<=65537; i++){
			RegistroSimbolos rsAux = new RegistroSimbolos();
			long proba = listaAux.get(i).getProbabilidad();
			rsAux.setProbabilidad(proba);
			listaAux.set(i, rsAux);
		}
		*/
		return listaAux;
	}
		

	private void crearArchivos(int orden) throws FileNotFoundException,	IOException {

		//creo el siguiente archivoModelo O con un solo elemento 
		this.archivoModelo = new ArchivoDiccionario("PPMCmodel_0.dat", true);

		PPMContexto contextoAux = new PPMContexto("vacio");
		//listaPPMContexto.add(contextoAux);

		archivoModelo.escribirRegistro(contextoAux);

		this.archivoModelo.cerrarArchivo();

		//ahora hago los otros 2 modelos pero vacios;
		this.archivoModelo = new ArchivoDiccionario("PPMCmodel_1.dat", true);
		this.archivoModelo.cerrarArchivo();
		this.archivoModelo = new ArchivoDiccionario("PPMCmodel_2.dat", true);
		this.archivoModelo.cerrarArchivo();

		switch (orden) {
		case 2:
			break;

		case 3:
			this.archivoModelo = new ArchivoDiccionario("PPMCmodel_3.dat", true);
			this.archivoModelo.cerrarArchivo();
			break;

		case 4:
			this.archivoModelo = new ArchivoDiccionario("PPMCmodel_3.dat", true);
			this.archivoModelo.cerrarArchivo();
						
			this.archivoModelo = new ArchivoDiccionario("PPMCmodel_4.dat", true);
			this.archivoModelo.cerrarArchivo();
			break;
		}
	}

	//dependiendo del orden actual (aca lo llamo nivelActual)
	private ArchivoDiccionario abrirArchivoModeloActual(int nivelActual)
			throws FileNotFoundException, IOException {
		ArchivoDiccionario archivo = null;
		switch (nivelActual) {
		case 0:
			archivo = new ArchivoDiccionario("PPMCmodel_0.dat", false);
			break;

		case 1:
			archivo = new ArchivoDiccionario("PPMCmodel_1.dat", false);
			break;

		case 2:
			archivo = new ArchivoDiccionario("PPMCmodel_2.dat", false);
			break;

		case 3:
			archivo = new ArchivoDiccionario("PPMCmodel_3.dat", false);
			break;

		default:
			archivo = new ArchivoDiccionario("PPMCmodel_4.dat", false);
			break;

		}
		return archivo;

	}

	//vaciamos lo q tenga la lista y cargamos la lista con lo q tenga el archivo
	private void cargarListaContextosModeloActual() throws IOException {
		int offset = 0;
		this.listaPPMContexto.clear();
		while (offset != this.archivoModelo.getOffSetEOF()) {
			PPMContexto contextoAux = new PPMContexto();
			this.archivoModelo.leerRegistro(contextoAux);
			this.listaPPMContexto.add(contextoAux);
			offset = (int) this.archivoModelo.getFilePointer();
		}

	}

	private void cierroArhivo(ArchivoDiccionario archivo, int nivelActual)
			throws IOException {
		switch (nivelActual) {
		case 0:
			archivo = new ArchivoDiccionario("PPMCmodel_0.dat", true);
			break;

		case 1:
			archivo = new ArchivoDiccionario("PPMCmodel_1.dat", true);
			break;

		case 2:
			archivo = new ArchivoDiccionario("PPMCmodel_2.dat", true);
			break;

		case 3:
			archivo = new ArchivoDiccionario("PPMCmodel_3.dat", true);
			break;

		default:
			archivo = new ArchivoDiccionario("PPMCmodel_4.dat", true);

		}

		for (PPMContexto contextoAux : listaPPMContexto) {
			archivo.escribirRegistro(contextoAux);

		}
		archivo.cerrarArchivo();
	}

	public CompresorAritmetico Salida() {
		return compresArit;
	}
	
}
